/*
 *
 * BITSI INPUT / OUTPUT, buttonbox, personal implementation
 * by Erik van den Boogert & Bram Daams & Pascal de Water
 *
 * Version 2.0 revision date 2010-05-18, 12:00h
 * Filename: box_V2
 *
 */
 
#include <EEPROM.h>

// start reading from the first byte (address 0) of the EEPROM
#include "WProgram.h"
void setup();
void loop();
void InPortWrite(byte code);
void OutPortWrite(byte code);
void OutPortOutputMode();
void InPortOutputMode();
void InPortInputMode();
void OutPortInputMode();
byte readInPort();
byte readOutPort();
int address = 0;

// available modes
byte mode;
byte change_mode      = 0;
byte buttonbox_stream = 1;
byte buttonbox_event  = 3;
byte BITSI            = 7;
byte pers_impl        = 15;
boolean NewCode       = false;


unsigned long TimeoutPulseLength = 10;  /* PulseLength in ms                 */
unsigned long TimeSend           = 0;   /* Debounce interval in ms           */
unsigned long TimeToSendOut      = 993;
unsigned long TimeoutPacingLED   = 500; /* Onboard Pacing LED interval in ms */

/* Reserved hardware wiring for enabling/disabling the HCT244 input buffer to I[8] */
int pinEnableBufIn = 13;

/* Reserved hardware wiring for forcing 27KOhm pull-up / pull-down to I[8] */
int pinPullUpDown  = 19;

/* There are 8 associated input bits D10=pin10, D11=pin11, D12=pin12
 * A0=pin14/AI0, A1=pin15/AI1 ,A2=pin16/AI2, A3=pin17/AI3, A4=pin18/AI3 */
int I[8] = { 10, 11, 12, 14, 15, 16, 17, 18};

/* declare SerialInChr to receive the desired Parallel_Out value in */
byte SerialInChr = 1;
byte SerialOutChr;

/* declare a time varable to remember the time when the Parallel_Out was set and to compare with TimeNow,
 * this way Parallel_Out can be implemented as a pulse with a desireded fixed pulseLength */
unsigned long TimeOnsetOut = 0;

/* declare bit state memory booleans to find out if input values have changed since the last looptest */
boolean State_I[8]      = { LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
boolean Prev_State_I[8] = { LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};



void setup()
{
  // configure input and output ports
  OutPortOutputMode();
  OutPortWrite(0);
  
  InPortInputMode();

  // configure onboard devices
  
  // enable input buffer
  pinMode(pinEnableBufIn, OUTPUT); 
  digitalWrite(pinEnableBufIn, LOW);
  
  // enable pullup resistors
  pinMode(pinPullUpDown, OUTPUT);
  digitalWrite(pinPullUpDown, HIGH);

  // blink stim leds
  for (byte i = 0; i < 4; i++) {
    OutPortWrite((B00000001 << i + 1 ) - 1);
    delay(200);
  }

  // read a byte from the current address of the EEPROM
  //mode = EEPROM.read(address);
  mode = EEPROM.read(0);

  // visual show mode to user
  for (byte i = 0; i < 4; i++) {
    OutPortWrite(mode);
    delay(200);
    OutPortWrite(0);
    delay(200);
  }
  
  // what mode are we running?
  if (mode == buttonbox_stream) {
    // initialize serial port
    Serial.begin(19200);
    Serial.println("Buttonbox streaming mode, Ready!");
  }
  else if (mode == buttonbox_event) {
    Serial.begin(19200);
    Serial.println("Buttonbox event mode, Ready!");
  }
  else if (mode == BITSI) {
    Serial.begin(115200);
    Serial.println("BITSI");
  }
  else if (mode == pers_impl) {
    Serial.begin(115200);
    Serial.println("personal_implementation, Ready!");
  }
  else {
    // write data for the first time
    mode = 0x01;
    EEPROM.write(address, mode);
    Serial.begin(19200);
    Serial.println("Buttonbox streaming mode, Ready!");
  }

}


//--- Forever Loop method --------------------------------------------------------------------------------------------------------------------------------------

void loop()
{
  byte index = 0;

  //--- buttonbox_stream ---------------------------------------------------------------------------------------------
  if (mode == buttonbox_stream) {
    /* check if data has been sent from the computer */
   if (Serial.available()) {
      //--- read the most recent byte
      SerialInChr = Serial.read();
      OutPortWrite(SerialInChr);
      NewCode = true;
    } 
  
     //--- sync every ms-----------------
     while (micros () < TimeSend) {}
      
     Serial.print(readInPort());
     TimeSend = micros() + TimeToSendOut;
  } 

  //--- buttonbox_event ---------------------------------------------------------------------------------------------
  else if (mode == buttonbox_event){
    /* check if data has been sent from the computer */
    if (Serial.available()) {
      //--- read the most recent byte
      SerialInChr = Serial.read();
      OutPortWrite(SerialInChr);
      NewCode = true;
    } 

    //--- sync every ms-----------------
    while (micros () < TimeSend) {}
 
    /* send data if there is any change */
    if (SerialOutChr != readInPort()){
      SerialOutChr = readInPort();
      Serial.print(SerialOutChr);
    }
    TimeSend = micros() + TimeToSendOut;
  }

  //--- BITSI ------------------------------------------------------------------------------------------------------
  else if (mode == BITSI){
    
    /* the debouncing or asking for the time takes to much processor time, events do not have the same time accuracy anymore
      tested this with ms timing of the buttonbox_stream, when it was implemented  ms timing was not reached*/ 
    byte index = 0;
    /* Process incoming serial characters
     *
     * The output bits are set immediately to reflect the value of the incoming serial character.
     */
  
    /* check if data has been sent from the computer */
    if (Serial.available()) {
      //--- read the most recent byte
      SerialInChr = Serial.read();
      OutPortWrite(SerialInChr);
      NewCode = true;
    }
  
    /* Process input bits
     *
     * The input bits are monitored for state changes. If bit 0 goes from low to high, a capital 'A' character
     * is sent back to the PC over the serial line.
     * When it changes back from high to low, a lowercase 'a' character is sent to the PC.
     * For bits 0 to 7, the characters A-H and a-h are sent respectively.
     *
     * It is possible to connect mechanical switches to each of the input, because the input bits are debounced.
     * After a bit changes state, it will be ignored for a debouncing interval of [TimeoutDebounce] miliseconds.
     */
  
    /* loop over the bits, to check their states */
    for (index = 0; index < 8; index = index + 1) {
      State_I[index] = digitalRead(I[index]);
      /* check for bit state change = egde, but not within debouncing interval */
      if (Prev_State_I[index] != State_I[index]) {
        /* respond with the corresponding character */
        if (State_I[index] == HIGH) {
          Serial.print(char(97 + index));
        }
        else {
          Serial.print(char(65 + index));
        }
  
        /* save new previous bit state */
        Prev_State_I[index] = State_I[index];
      }
    }
  }

  //--- personal implementation ---------------------------------------------------------------------------------------------
  else if (mode == pers_impl) {
   // write your code here
    if (Serial.available()) {
      //--- read the most recent byte
      SerialInChr = Serial.read();
      Serial.print(SerialInChr);
    } 
  }


  //--- change mode ---------------------------------------------------------------------------------------------
  //00x    = buttonbox_stream
  //000x   = buttonbox_event
  //0000x  = BITSI
  //00000x = personal implementation
  if ((SerialInChr == 48) && (NewCode)){
    NewCode = false;
    change_mode++;
    Serial.print(char(change_mode + 48));
  }
  else if ((change_mode > 1) && (SerialInChr != 48)) {
    if (change_mode == 2) {
       EEPROM.write(0, buttonbox_stream);
       Serial.print("new mode: stream, reboot!");
       setup();
    }
    else if (change_mode == 3) {
       EEPROM.write(0, buttonbox_event);
       Serial.print("new mode: event, reboot!");
       setup();
    }
    else if (change_mode == 4) {
       EEPROM.write(0, BITSI);
       Serial.print("new mode: BITSI, reboot!");
       setup();
    }
    else if (change_mode == 5) {
       EEPROM.write(0, pers_impl);
       Serial.print("new mode: personal, reboot!");
       setup();
    }
    change_mode = 0;
  }
}


// todo: implement writing to the input port
void InPortWrite(byte code)
{
  
}

void OutPortWrite(byte code)
{
  byte b, d;
  
  // perform bit calculations before updating the ports
  b = (code & B11000000) >> 6 | (PINB & B11111100);
  d = (code & B00111111) << 2;
  
  // update the ports
  PORTB = b;
  PORTD = d;
}

void OutPortOutputMode()
{
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

void InPortOutputMode()
{
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  pinMode(18, OUTPUT);
}

void InPortInputMode()
{
  // configure all the pins of the input port as inputs
  pinMode(10, INPUT);    
  pinMode(11, INPUT);
  pinMode(12, INPUT);    
  pinMode(14, INPUT);    
  pinMode(15, INPUT);    
  pinMode(16, INPUT);    
  pinMode(17, INPUT);    
  pinMode(18, INPUT);

  // disable internal pullup
  digitalWrite(10, HIGH);  
  digitalWrite(11, HIGH);  
  digitalWrite(12, HIGH);  
  digitalWrite(14, HIGH);  
  digitalWrite(15, HIGH);  
  digitalWrite(16, HIGH);  
  digitalWrite(17, HIGH);  
  digitalWrite(18, HIGH);
}

void OutPortInputMode()
{
  // configure all the pins of the output port as inputs ;-)
  pinMode(2, INPUT);    
  pinMode(3, INPUT);
  pinMode(4, INPUT);    
  pinMode(5, INPUT);    
  pinMode(6, INPUT);    
  pinMode(7, INPUT);    
  pinMode(8, INPUT);    
  pinMode(9, INPUT);

  // enable internal pullup
  digitalWrite(2, HIGH);  
  digitalWrite(3, HIGH);  
  digitalWrite(4, HIGH);  
  digitalWrite(5, HIGH);  
  digitalWrite(6, HIGH);  
  digitalWrite(7, HIGH);  
  digitalWrite(8, HIGH);  
  digitalWrite(9, HIGH);
}


byte readInPort()
{
  byte b, c;
  
  b = PINB;
  c = PINC;
  
  return ((b & B00011100) >> 2) | 
    ((c & B00011111)      << 3);
  /*          ^^^^^^^^  */
  
}

byte readOutPort()
{
  byte c, d;
  
  c = PINC;
  d = PIND;
  
  return ((d & B11111100) >> 2) |
 ((c & B00000011) << 6);
  /*          ^^^^^^^^  */

}


int main(void)
{
	init();

	setup();
    
	for (;;)
		loop();
        
	return 0;
}

